// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.neuronbit.businessflow.intellij.index;

import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.util.SlowOperations;
import com.intellij.util.indexing.*;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import com.neuronbit.businessflow.intellij.BpmFileType;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.StringReader;
import java.util.*;


/**
 * The type Flow class index.
 */
public class FlowClassIndex extends ScalarIndexExtension<String> {
    private static final SAXParser SAX_PARSER = createParser();

    private static SAXParser createParser() {
        try {
            return SAXParserFactory.newInstance().newSAXParser();
        } catch (Exception var1) {
            return null;
        }
    }

    /**
     * The constant NAME.
     */
    @NonNls
    public static final ID<String, Void> NAME = ID.create("com.neuronbit.businessflow.intellij.index.FlowClassIndex");
    private final MyDataIndexer myDataIndexer = new MyDataIndexer();

    @Override
    @NotNull
    public ID<String, Void> getName() {
        return NAME;
    }

    @Override
    @NotNull
    public DataIndexer<String, Void, FileContent> getIndexer() {
        return myDataIndexer;
    }

    @NotNull
    @Override
    public KeyDescriptor<String> getKeyDescriptor() {
        return EnumeratorStringDescriptor.INSTANCE;
    }

    @NotNull
    @Override
    public FileBasedIndex.InputFilter getInputFilter() {
        return new DefaultFileTypeSpecificInputFilter(BpmFileType.INSTANCE);
    }

    @Override
    public boolean dependsOnFileContent() {
        return true;
    }

    @Override
    public int getVersion() {
        return 0;
    }

    private static class MyDataIndexer implements DataIndexer<String, Void, FileContent> {
        @Override
        @NotNull
        public Map<String, Void> map(@NotNull final FileContent inputData) {
            String className = null;
            try {
                className = getBoundClassName(inputData.getContentAsText().toString());
            } catch (Exception e) {
                // ignore
            }
            if (className != null) {
                return Collections.singletonMap(className, null);
            }
            return Collections.emptyMap();
        }

        /**
         * Gets bound class name.
         *
         * @param formFileContent the form file content
         * @return the bound class name
         * @throws Exception the exception
         */
        public synchronized String getBoundClassName(String formFileContent) throws Exception {

            final String[] className = new String[]{null};

            try {
                SAX_PARSER.parse(new InputSource(new StringReader(formFileContent)), new DefaultHandler() {
                    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                        if ("com.neuronbit.businessflow.intellij.model.Call".equals(qName)) {
                            className[0] = attributes.getValue("", "type");
                            throw new SAXException("stop parsing");
                        }
                    }
                });
            } catch (Exception var3) {
            }

            return className[0];
        }
    }

    /**
     * Find forms bound to class list.
     *
     * @param project   the project
     * @param className the class name
     * @return the list
     */
    public static List<PsiFile> findFormsBoundToClass(Project project, String className) {
        return findFormsBoundToClass(project, className, ProjectScope.getAllScope(project));
    }

    /**
     * Find forms bound to class list.
     *
     * @param project   the project
     * @param className the class name
     * @param scope     the scope
     * @return the list
     */
    public static List<PsiFile> findFormsBoundToClass(final Project project,
                                                      final String className,
                                                      final GlobalSearchScope scope) {
        try (AccessToken ignore = SlowOperations.knownIssue("IDEA-307701, EA-648610")) {
            return ReadAction.compute(() -> {
                final Collection<VirtualFile> files;
                try {
                    files = FileBasedIndex.getInstance().getContainingFiles(NAME, className,
                            GlobalSearchScope.projectScope(project).intersectWith(scope));
                } catch (IndexNotReadyException e) {
                    return Collections.emptyList();
                }
                if (files.isEmpty()) return Collections.emptyList();
                List<PsiFile> result = new ArrayList<>();
                for (VirtualFile file : files) {
                    if (!file.isValid()) continue;
                    PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
                    if (psiFile != null) {
                        result.add(psiFile);
                    }
                }
                return result;
            });
        }
    }

    /**
     * Find forms bound to class list.
     *
     * @param project  the project
     * @param psiClass the psi class
     * @return the list
     */
    public static List<PsiFile> findFormsBoundToClass(Project project, @NotNull PsiClass psiClass) {
        String qName = FlowReferencesSearcher.getQualifiedName(psiClass);
        if (qName == null) return Collections.emptyList();
        return findFormsBoundToClass(project, qName);
    }

    /**
     * Find forms bound to class list.
     *
     * @param project  the project
     * @param psiClass the psi class
     * @param scope    the scope
     * @return the list
     */
    public static List<PsiFile> findFormsBoundToClass(Project project, PsiClass psiClass, GlobalSearchScope scope) {
        String qName = FlowReferencesSearcher.getQualifiedName(psiClass);
        if (qName == null) return Collections.emptyList();
        return findFormsBoundToClass(project, qName, scope);
    }
}
